#! /bin/sh
# Copyright (C) 2010-2021 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

# Check that building from, or installing to, directories with shell
# metacharacters succeed.
# Original report from James Amundson about file names with spaces.
# Other characters added by Paul Eggert.

. test-init.sh

# Usage: is_in_list ITEM [LIST...]
is_in_list ()
{
  item=$1; shift;
  case " $* " in
    *[\ \	]"$item"[\ \	]*) return 0;;
    *) return 1;;
  esac
}

# Helper subroutine for test data definition.
# Usage: define_problematic_string NAME STRING
define_problematic_string ()
{
  tst=$1; shift
  eval "instspc__$tst=\$1" \
    || fatal_ "define_problematic_string: bad argument: '$tst'"
  shift
  all_test_names_list="$all_test_names_list $tst"
  # Some of the "problematic" characters cannot be used in the name of
  # a build or install directory on a POSIX host.  These lists should
  # be empty, but are not due to limitations in Autoconf, Automake, Make,
  # M4, or the shell.
  if is_in_list fail-builddir "$@"; then
    builddir_xfails="$builddir_xfails $tst"
  fi
  if is_in_list fail-destdir "$@"; then
    destdir_xfails="$destdir_xfails $tst"
  fi
}

# Be sure to avoid interferences from the environment.
all_test_names_list=''
builddir_xfails=''
destdir_xfails=''

expected_to_fail ()
{
   case $1 in
     build) is_in_list "$2" $builddir_xfails;;
      dest) is_in_list "$2" $destdir_xfails;;
         *) fatal_ "incorrect 'expected_to_fail' usage";;
   esac
}

# Helper subroutines for creation of input data files.
create_input_data ()
{
  mkdir sub

  unindent >> configure.ac << 'EOF'
    AC_PROG_CC
    AM_PROG_AR
    AC_PROG_RANLIB
    AC_OUTPUT
EOF

  : > sub/base.h
  : > sub/nobase.h
  : > sub/base.dat
  : > sub/nobase.dat
  : > sub/base.sh
  : > sub/nobase.sh

  unindent > source.c << 'EOF'
    int
    main (int argc, char **argv)
    {
      return 0;
    }
EOF

  unindent > Makefile.am << 'EOF'
    foodir = $(prefix)/foo
    fooexecdir = $(prefix)/foo

    foo_HEADERS = sub/base.h
    nobase_foo_HEADERS = sub/nobase.h

    dist_foo_DATA = sub/base.dat
    nobase_dist_foo_DATA = sub/nobase.dat

    dist_fooexec_SCRIPTS = sub/base.sh
    nobase_dist_fooexec_SCRIPTS = sub/nobase.sh

    fooexec_PROGRAMS = sub/base
    nobase_fooexec_PROGRAMS = sub/nobase
    sub_base_SOURCES = source.c
    sub_nobase_SOURCES = source.c

    fooexec_LIBRARIES = sub/libbase.a
    nobase_fooexec_LIBRARIES = sub/libnobase.a
    sub_libbase_a_SOURCES = source.c
    sub_libnobase_a_SOURCES = source.c

    .PHONY: test-inst
    test-inst: install
	test   -f '$(DESTDIR)/$(file)-prefix/foo/sub/nobase.h'
	test ! -f '$(DESTDIR)/$(file)-prefix/foo/nobase.h'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/base.h'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/sub/nobase.dat'
	test ! -f '$(DESTDIR)/$(file)-prefix/foo/nobase.dat'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/base.dat'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/sub/nobase.sh'
	test ! -f '$(DESTDIR)/$(file)-prefix/foo/nobase.sh'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/base.sh'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/sub/nobase$(EXEEXT)'
	test ! -f '$(DESTDIR)/$(file)-prefix/foo/nobase$(EXEEXT)'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/base$(EXEEXT)'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/sub/libnobase.a'
	test ! -f '$(DESTDIR)/$(file)-prefix/foo/libnobase.a'
	test   -f '$(DESTDIR)/$(file)-prefix/foo/libbase.a'
EOF

  $ACLOCAL     || framework_failure_ "aclocal failed"
  $AUTOCONF    || framework_failure_ "autoconf failed"
  $AUTOMAKE -a || framework_failure_ "automake failed"
}

# ================= #
#  Test data begin  #
# ----------------- #

# Some control characters that are white space.
bs=''   # back space
cr=''   # carriage return
ff=''   # form feed
ht='	' # horizontal tab
lf='
'         # line feed (aka newline)

# Hack to save typing and make code visually clearer.
def=define_problematic_string

$def    squote          \'          fail-builddir  fail-destdir
$def    dquote          '"'         fail-builddir  fail-destdir
$def    bquote          '`'         fail-builddir  fail-destdir
$def    sharp           '#'         fail-builddir  fail-destdir
$def    dollar          '$'         fail-builddir  fail-destdir
$def    bang            '!'
$def    bslash          '\'         fail-builddir
$def    ampersand       '&'         fail-builddir
$def    percent         '%'
$def    leftpar         '('
$def    rightpar        ')'
$def    pipe            '|'
$def    caret           '^'
$def    tilde           '~'
$def    qmark           '?'
$def    star            '*'
$def    plus            '+'
$def    minus           '-'
$def    comma           ','
$def    colon           ':'
$def    semicol         ';'
$def    equal           '='
$def    less            '<'
$def    more            '>'
$def    at              '@'
$def    lqbrack         '['
$def    rqbrack         ']'
$def    lcbrack         '{'
$def    rcbrack         '}'
$def    space           ' '
$def    tab             "$ht"
$def    linefeed        "$lf"       fail-builddir  fail-destdir
$def    backspace       "$bs"
$def    formfeed        "$ff"
$def    carriageret     "$cr"
$def    quadrigraph0    '@&t@'      fail-builddir
$def    quadrigraph1    '@<:@'
$def    quadrigraph2    '@:>@'
$def    quadrigraph3    '@S|@'
$def    quadrigraph4    '@%:@'
$def    a_b             'a b'
$def    a__b            'a  b'
$def    a_lf_b          "a${lf}b"   fail-builddir  fail-destdir
$def    dotdotdot       '...'
$def    dosdrive        'a:'
$def    miscglob1       '?[a-z]*'
$def    miscglob2       '.*?[0-9]'

unset def

# --------------- #
#  Test data end  #
# =============== #

# Allow the user to select a subset of the tests.
if test $# -gt 0; then
  test_names_list=$*
  for test_name in $test_names_list; do
    case " $all_test_names_list " in
      *" $test_name "*);;
      *) fatal_ "invalid user-specified test_name '$test_name'"
    esac
  done
  # We need to determine the TAP plan adaptively.
  n=$(for t in $test_names_list; do echo $t; done | wc -l)
  plan_ $(($n * 2)) # Two tests per "problematic string".
  unset n
else
  test_names_list=$all_test_names_list
  # Prefer static TAP plan if possible, it minimizes the chance of errors.
  plan_ 94
fi

ocwd=$(pwd) || fatal_ "getting current working directory"

create_input_data

for test_name in $test_names_list; do

  eval "test_string=\${instspc__$test_name}" \
    || fatal_ "invalid test name: '$test_name'"

  if test x"$test_string" = x; then
    if test x"$test_name" != xcarriageret; then
      fatal_ "invalid test name: '$test_name'"
    else
      # MSYS version 1.0.17 still mishandles carriage returns; see
      # automake bug#7849.
      skip_ -r "carriage-return treated as null char" "$test_name in builddir"
      skip_ -r "carriage-return treated as null char" "$test_name in destdir"
      continue
    fi
  fi

  # Skip the next checks if this system doesn't support the required
  # characters in file names.

  mkdir "./$test_string" || {
    skip_ -r "mkdir failed" "$test_name in builddir"
    skip_ -r "mkdir failed" "$test_name in destdir"
    continue
  }

  case $test_string in
  *:*)
    # On MSYS 1.0.17, "mkdir ./a:" creates ./a, and "cd ./a:" takes you
    # to a strange directory with pwd equal to "a".  But only for
    # interactive shells.  Or something?  In this script, "cd ./a:" fails
    # on MSYS.  Marvelous.
    ( cd "./$test_string" ) || {
      rmdir "./$test_string" || fatal_ "removing directory"
      skip_ -r "cd failed" "$test_name in builddir"
      skip_ -r "cd failed" "$test_name in destdir"
      continue
    }
    ;;
  esac

  # Where are the "weird" characters going to be used, in $(builddir)
  # or in $(DESTDIR)?  They are always going to be used in $(prefix)
  # though; should we maybe separate this into a dedicated check?
  for where in build dest; do

    case $where in
      build)
        build=./$test_string
        dest=$ocwd/dest-$test_name
        ;;
      dest)
        build=build-$test_name
        # Also use $test_name in the definition of $dest, to avoid
        # interferences among different tests in case $test_string
        # is strangely munged (which is not unexpected, considering
        # how tricky its characters are).  With some shells, this
        # has already happened (at least on OpenIndiana 11 and on
        # Solaris 10).
        dest=$ocwd/dest-$test_name/$test_string
        mkdir "$build" || fatal_ "cannot create '$build'"
        ;;
      *)
        fatal_ "invalid where '$where'"
        ;;
    esac

    cd "$build" || fatal_ "cannot chdir into '$build'"

    # Some make implementations eliminate leading and trailing whitespace
    # from macros passed on the command line, and some eliminate leading
    # whitespace from macros set from environment variables, so prepend
    # './' and use the latter here.
    r=ok
    ../configure --prefix "/$test_string-prefix" \
      && $MAKE all \
      && DESTDIR="$dest" file="./$test_string" $MAKE test-inst \
      || r='not ok'

    description="$test_name in ${where}dir"
    if expected_to_fail "$where" "$test_name"; then
      directive=TODO
      reason="long-standing limitation"
    else
      directive=
      reason=
    fi
    # Test case outcome is here.
    result_ "$r" -D "$directive" -r "$reason" -- "$description"

    cd "$ocwd" || fatal_ "cannot chdir back to test directory"

    # Remove subdirectories for tests that have passed, to avoid ending up
    # with a too big test directory.  This is especially important since
    # some tests in this tests are expected to fail, and this will cause
    # the test directory not to be removed when the script terminates.
    if not am_keeping_testdirs && test "$r" = ok; then
      rm_rf_ "$build" "$dest" || fatal_ "removing temporary subdirectory"
    fi

    : For shells with busted 'set -e'.

  done # $instspc_action

done # $test_name

:
